package cn.wapache.time.calendar;

import static java.util.Calendar.MONTH;
import static java.util.Calendar.DAY_OF_MONTH;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.TimeZone;

/**
 * 排除了每年的一部分日子的日历，譬如可以用来排除法定节日。
 * 年时分秒都不考虑的。
 * 
 *  原类名: AnnualCalendar
 *  新类名: ExcludeDaysOfYearCalendar
 * 
 * 这个日历只考虑月份和几号(月日), 年时分秒都不考虑
 *  
 * 在BaseCalendar的基础上,加入
 * 1. isDayExcluded 指定的日期(月份和几号)是否被排除了
 * 2. 
 * 
 * 改进
 * 1. 用MonthDay代替java.util.Calendar
 * 
 * 
 */
public class ExcludeMonthDaysCalendar extends BaseCalendar {

    static final long serialVersionUID = 7346867105876610961L;

    private ArrayList<java.util.Calendar> excludeDays = new ArrayList<>();

    // true, if excludeDays is sorted
    private boolean dataSorted = false;

    public ExcludeMonthDaysCalendar() {}

    public ExcludeMonthDaysCalendar(Calendar baseCalendar) {
        super(baseCalendar);
    }

    public ExcludeMonthDaysCalendar(TimeZone timeZone) {
        super(timeZone);
    }

    public ExcludeMonthDaysCalendar(Calendar baseCalendar, TimeZone timeZone) {
        super(baseCalendar, timeZone);
    }

    @Override
    public Object clone() {
        ExcludeMonthDaysCalendar clone = (ExcludeMonthDaysCalendar) super.clone();
        clone.excludeDays = new ArrayList<>(excludeDays);
        return clone;
    }

    public ArrayList<java.util.Calendar> getDaysExcluded() {
        return excludeDays;
    }

    /**
     * 指定的日期(只包含月份和日期, 年/时分秒都不考虑的)是否被排除了
     * <p>
     * Return true, if day is defined to be exluded.
     * </p>
     */
    public boolean isDayExcluded(java.util.Calendar day) {
    	
        if (day == null) {
            throw new IllegalArgumentException("Parameter day must not be null");
        }
        // Check baseCalendar first
        if (! super.isTimeIncluded(day.getTimeInMillis())) {
        	return true; // 压根不在上一个日历的范围内,直接返回true
        }
        
        if (dataSorted == false) { // 按照月日从小到大排序
            Collections.sort(excludeDays, JdkCalendar.monthDayComparator);
            dataSorted = true;
        }
        
        int dmonth = day.get(MONTH);
        int dday = day.get(DAY_OF_MONTH);
        for (java.util.Calendar cl : excludeDays) {
            if (dmonth  < cl.get(MONTH)) { return false; }
            if (dday   != cl.get(DAY_OF_MONTH)) { continue; }
            if (dmonth != cl.get(MONTH)) { continue; }
            return true;
		}

        return false;
    }

    public void setDaysExcluded(ArrayList<java.util.Calendar> days) {
        if (days == null) {
            excludeDays = new ArrayList<java.util.Calendar>();
        } else {
            excludeDays = days;
        }

        dataSorted = false;
    }

    /**
     * <p>
     * Redefine a certain day to be excluded (true) or included (false).
     * </p>
     */
    public void setDayExcluded(java.util.Calendar day, boolean exclude) {
        if (exclude) {
            if (isDayExcluded(day)) {
                return;
            }

            excludeDays.add(day);
            dataSorted = false;
        } else {
            if (!isDayExcluded(day)) {
                return;
            }

            removeExcludedDay(day, true);
        }
    }

    /**
     * Remove the given day from the list of excluded days
     *  
     * @param day the day to exclude
     */
    public void removeExcludedDay(java.util.Calendar day) {
        removeExcludedDay(day, false);
    }
    
    private void removeExcludedDay(java.util.Calendar day, boolean isChecked) {
    	// 不检查 且 没有被排除, 直接返回
        if (! isChecked && ! isDayExcluded(day)) { return; }
        // Fast way, see if exact day object was already in list
        if (this.excludeDays.remove(day)) { return; }
        //因为jdk没有月日这个表示方式,calendar是包含年等等信息的,不能简单的用remove
        int dmonth = day.get(MONTH);
        int dday = day.get(DAY_OF_MONTH);
        for (java.util.Calendar cl : excludeDays) {
        	if (dmonth != cl.get(MONTH)) { continue; }
            if (dday   != cl.get(DAY_OF_MONTH)) { continue; }
            this.excludeDays.remove(day);
            //break;
		}
    }
    
    /**
     * <p>
     * Determine whether the given time (in milliseconds) is 'included' by the
     * Calendar.
     * </p>
     * 
     * <p>
     * Note that this Calendar is only has full-day precision.
     * </p>
     */
    @Override
    public boolean isTimeIncluded(long timeStamp) {
        // Test the base calendar first. Only if the base calendar not already
        // excludes the time/date, continue evaluating this calendar instance.
        if (super.isTimeIncluded(timeStamp) == false) { return false; }

        java.util.Calendar day = JdkCalendar.getCalendar(getTimeZone(), timeStamp);

        return !(isDayExcluded(day));
    }

    /**
     * <p>
     * Determine the next time (in milliseconds) that is 'included' by the
     * Calendar after the given time. Return the original value if timeStamp is
     * included. Return 0 if all days are excluded.
     * </p>
     * 
     * <p>
     * Note that this Calendar is only has full-day precision.
     * </p>
     * @return 返回 timeStamp本身 或者 后续某一天的凌晨零点的时间戳
     */
    @Override
    public long getNextIncludedTime(long timeStamp) {
        // Call base calendar implementation first
        long baseTime = super.getNextIncludedTime(timeStamp);
        if ((baseTime > 0) && (baseTime > timeStamp)) {
            timeStamp = baseTime;
        }

        // Get timestamp for 00:00:00
        java.util.Calendar day = JdkCalendar.getStartOfDay(this.getTimeZone(), timeStamp);
        if (isDayExcluded(day) == false) { 
            return timeStamp; // return the original value
        }

        while (isDayExcluded(day) == true) {
            day.add(java.util.Calendar.DATE, 1);
        }

        return day.getTime().getTime();
    }
}

